﻿using SmartApi.Annotations;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

namespace System
{
    internal class ObjectConverter
    {
        public static ObjectConverter Instance = new ObjectConverter();
        private static readonly object _syncObj = new object();
        private static Dictionary<string, Func<object, Dictionary<string, object>>> _cachedConvert = new Dictionary<string, Func<object, Dictionary<string, object>>>();
        private readonly static Type _dicType = typeof(Dictionary<string, object>);
        private readonly static MethodInfo _addItemDicMentod = _dicType.GetMethod(nameof(Dictionary<string, object>.Add));
        private readonly static ConstructorInfo _dicCtor = _dicType.GetConstructor(Type.EmptyTypes);
        public IDictionary<string, object> ToDictionary(object target)
        {
            var convert = CreateDicConvertImpl(target);
            return convert(target);
        }

        private Func<object, Dictionary<string, object>> GetConvert(object source)
        {
            string cachedKey = "NULL";
            if (source != null)
            {
                cachedKey = source.GetType().FullName;
            }
            if (!_cachedConvert.ContainsKey(cachedKey))
            {
                lock (_syncObj)
                {
                    if (!_cachedConvert.ContainsKey(cachedKey))
                    {
                        var impl = CreateDicConvertImpl(source);
                        _cachedConvert.Add(cachedKey, impl);
                    }
                }
            }
            return _cachedConvert[cachedKey];
        }

        private Func<object, Dictionary<string, object>> CreateDicConvertImpl(object source)
        {
            if (source == null)
            {
                return (obj) => { return new Dictionary<string, object>(); };
            }
            var sourceType = source.GetType();
            Type returnType = _dicType;
            var addItemMethod = _addItemDicMentod;
            var sourceProps = sourceType.GetProperties().Where(p => p.CanRead);
            var dynamicMethod = new DynamicMethod("ObjToDic_" + Guid.NewGuid().ToString("N"), returnType, new[] { typeof(object) }, sourceType, true);
            var iLGenerator = dynamicMethod.GetILGenerator();
            iLGenerator.DeclareLocal(returnType);
            iLGenerator.Emit(OpCodes.Newobj, _dicCtor);
            iLGenerator.Emit(OpCodes.Stloc_0);
            foreach (var prop in sourceProps)
            {
                var paramName = prop.Name;
                var paramAttr = prop.GetCustomAttribute<ParamAttribute>();
                if (!String.IsNullOrEmpty(paramAttr?.Alias))
                {
                    paramName = paramAttr.Alias;
                }
                iLGenerator.Emit(OpCodes.Ldloc_0); //[dic]
                iLGenerator.Emit(OpCodes.Ldstr, paramName);//[dic][prop-name]
                iLGenerator.Emit(OpCodes.Ldarg_0);//[dic][prop-name][sourceObj]
                iLGenerator.Emit(OpCodes.Call, prop.GetMethod);//[dic][prop-name][prop-value]
                if (prop.PropertyType.IsValueType)
                {
                    iLGenerator.Emit(OpCodes.Box, prop.PropertyType);
                }
                iLGenerator.Emit(OpCodes.Call, addItemMethod);//[empty]
            }
            iLGenerator.Emit(OpCodes.Ldloc_0);
            iLGenerator.Emit(OpCodes.Ret);
            var funcType = Linq.Expressions.Expression.GetFuncType(typeof(object), returnType);
            return (Func<object, Dictionary<string, object>>)dynamicMethod.CreateDelegate(funcType);
        }
    }
}
